/*
 * Sempre que se pretender adicionar um novo ficheiro
 * deve-se adicona a respectiva TSFile ao concurrentHashMap
 * e de seguida começa a enviar os dados atrvés encapsulados
 * na classe Tuplle, esses dados vão entrar para uma queue onde
 * esperam que uma thread, da pool os retire. A thread verifica se
 * existe o elemento identificado pelo tuplo na concurrentHashMap de forma a
 * poder esccrever para ficheiro esses dados
 */

package core;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import protocol.TSFile;

/**
 *
 * @author Flavio
 */
public class PoolThreads {
    
    private ArrayList<WorkerThread> idle;

    private ArrayList<SenderThread> senders;
    //método para is lendo e enviando dados int key, ProtocoloSD protocolo


    private ConcurrentHashMap<Integer, TSFile> mapa;

    //Queue de tupple para escrita em ficheiro
    //Posteriormente este Tupple até pode vir a ser outra coisa
    private LinkedList<Tupple> queue;

    private LinkedList<TuppleSender> queueSender;

    public PoolThreads(int size, int senders){
        idle = new ArrayList<WorkerThread>();
        this.senders = new ArrayList<SenderThread>();
        queue = new LinkedList<Tupple>();
        queueSender = new LinkedList<TuppleSender>();
        mapa = new ConcurrentHashMap<Integer, TSFile>();
        for(int i = 0; i < size; i++){
            WorkerThread w = new WorkerThread(this);
            w.setName("worker + "+i);
            idle.add(w);
            w.start();
        }

        for(int i = 0; i < senders; i++){
            SenderThread w = new SenderThread(this);
            w.setName("sender + "+i);
            this.senders.add(w);
            w.start();
        }
    }

    public ConcurrentHashMap<Integer, TSFile> getMapa(){
        return mapa;
    }

    public LinkedList<Tupple> getQueue(){
        return queue;
    }

    public LinkedList<TuppleSender> getQueueSender(){
        return queueSender;
    }

    /**
     * Adiciona dados a uma queue de tarefas, para serem executados por uma das
     * threads disponiveis.
     * Este método deve ser precedido de um addTSFile para que a thread que escreve
     * saiba como escrever e quando é que acabou de escrever
     * @param task
     */
    public void execute(int key, byte[] data){
        synchronized(queue){
            Tupple task = new Tupple(key, data);
            queue.add(task);
            queue.notify();
        }
    }

    /**
     * Método que se encarrega de adicionar a tarefa para enviar dados a um cliente
     */
    public void execute(int key, ProtocolSD proto){
        synchronized(queueSender){
            queueSender.add(new TuppleSender(key, proto));
            queueSender.notify();
        }
    }

    /**
     * Adiciona uma referência de uma TSFile para que sempre que
     * seja adicionado um Tupple á queue a workerthread que escreve saiba possa
     * executar a tarerefa com sucesso
     * @param nova
     */
    public void addTSFile(TSFile nova){
        mapa.put(nova.getFileTag().getKey(), nova);
    }

    /**
     * Remove uma TSFile do mapa, pode ser usado para o caso em que se pret6ernda
     * cancelar um download, todos os Tupple exsitentes na queue vão ser dscartatados
     * também
     * @param key
     */
    public TSFile removeTSFile(int key){
        return mapa.remove(key);
    }

    /**
     * Remove o ficheiro identificado pela key e caso este esteja no mapa
     * remove-o dele, desta forma todas as tarefas de escrita para o ficheiro
     * vão ser descartadas pelas threads
     * @param key
     * @return true caso consiga remover o ficheiro, false caso contrário
     */
    public boolean deleteTSFile(int key){
        TSFile file = this.removeTSFile(key);
        if(file!=null){
            file.close();
            return file.delete();
        }
        else {
            File f = new File(new Integer(key).toString());
            return f.delete();
        }
    }

    public TSFile get(int key){
        return mapa.get(key);
    }

    public void addWorkerSender(){
        SenderThread n = new SenderThread(this);
        senders.add(n);
        n.start();
    }

    public void addWorkerWriter(){
        WorkerThread n = new WorkerThread(this);
        idle.add(n);
        n.start();
    }

    public void removeWorkerSender(){
        if(!senders.isEmpty()){
           SenderThread sender = senders.remove(0);
           sender.interrupt();
        }
    }

    public void removeWorkerWriter(){
        if(!idle.isEmpty()) {
            WorkerThread w = idle.remove(0);
            w.interrupt();
        }
    }

    public void returnErrorOnWriting(int key){
        //TODO: Saber o que fazer nesta situação
    }

    /**
     * Método a ser invocado por uma thread sempre que tenha acabado de escrver
     * um ficheiro com sucesso
     */
    public void returnOnSuccessWriting(TSFile file){
        //TODO: Acabou de escrever com sucesso, falta notificar o master server
    }

    void returnErrorOnSending(TSFile file) {
        //TODO: Saber o que fazer quando não se consegue enviar
    }
}
